﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using Microsoft.PowerShell.Host.ISE;
using System.Management.Automation.Language;
using System.Collections;
using System.Text.RegularExpressions;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace ISEJumpListAddon
{
    /// <summary>
    /// Interaction logic for AddonTab.xaml
    /// </summary>

    public partial class AddonTab : UserControl, IAddOnToolHostObject 
    {
        public static string name = "JumpList";
        private ObjectModelRoot hostobject;
        // TODO Make that a list of lists?
        // E.g. a regionsList object that contains the list + its name + a "show" member + sort methods...
        private ObservableCollection<RegionPosition> MyRegionsList;
        private ObservableCollection<FunctionPosition> MyFunctionsList;
        private ObservableCollection<TODOPosition> MyTODOsList;
        private ObservableCollection<ErrorPosition> MyErrorsList;
        private ObservableCollection<VariablePosition> MyVariableList;
        public AddonTab()
        {
            InitializeComponent();
            MyComboBox.Items.Clear();
            MyComboBox.Items.Add("Functions");
            MyComboBox.Items.Add("Regions");
            MyComboBox.Items.Add("TODO");
            MyComboBox.Items.Add("Errors");
            MyComboBox.Items.Add("Variable");
            MyComboBox.SelectedValue = "Functions";
            MyRegionsList = new ObservableCollection<RegionPosition>();
            MyFunctionsList = new ObservableCollection<FunctionPosition>();
            MyTODOsList = new ObservableCollection<TODOPosition>();
            MyErrorsList = new ObservableCollection<ErrorPosition>();
            MyVariableList = new ObservableCollection<VariablePosition>();
            MyListView.ItemsSource = MyFunctionsList;
            // HostObject is null at this point, should be obvious!
            // ISE first creates an instance of the addon, then sets its hostobject
        }

        void CurrentFile_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (HostObject.CurrentPowerShellTab.Files != null)
            {
                if (HostObject.CurrentPowerShellTab.Files.SelectedFile != null)
                {
                    HostObject.CurrentPowerShellTab.Files.SelectedFile.PropertyChanged += new PropertyChangedEventHandler(SelectedFile_PropertyChanged);
                }
            }
        }

        void CurrentPowerShellTab_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if ((e.PropertyName == "Files") || (e.PropertyName == "LastEditorWithFocus"))
            {
                if (hostobject.CurrentPowerShellTab.Files.SelectedFile != null)
                {
                    ParseCodeAndFillList();
                }
            }
        }

        void Files_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (hostobject.CurrentPowerShellTab.Files.SelectedFile != null)
            {
                ParseCodeAndFillList();
                hostobject.CurrentPowerShellTab.Files.SelectedFile.PropertyChanged += SelectedFile_PropertyChanged;
            }
        }

        void SelectedFile_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (hostobject.CurrentPowerShellTab.Files.SelectedFile != null)
            {
                ParseCodeAndFillList();
            }
        }

        // ISE uses this to link the addon to itself
        // Any initialization that needs to access hostobject should be done here
        public ObjectModelRoot HostObject
        {
            get
            {
                return this.hostobject;
            }
            set
            {
                this.hostobject = value;
                if (hostobject.CurrentPowerShellTab.Files.SelectedFile != null)
                {
                    hostobject.CurrentPowerShellTab.Files.SelectedFile.PropertyChanged += SelectedFile_PropertyChanged;
                }
                HostObject.CurrentPowerShellTab.Files.CollectionChanged += Files_CollectionChanged;
                HostObject.CurrentPowerShellTab.PropertyChanged += CurrentPowerShellTab_PropertyChanged;
            }
        }

        private void ParseCodeAndFillList()
        {
            if (HostObject != null)
            {
                if (HostObject.CurrentPowerShellTab.Files.SelectedFile != null)
                {
                    MyRegionsList.Clear();
                    MyTODOsList.Clear();
                    MyFunctionsList.Clear();
                    MyErrorsList.Clear();
                    MyVariableList.Clear();
                    MyLabel.Content = HostObject.CurrentPowerShellTab.Files.SelectedFile.DisplayName;
                    // TODO Make that dynamic
                    string variableName = HostObject.CurrentPowerShellTab.Files.SelectedFile.Editor.SelectedText;
                    
                    string s = HostObject.CurrentPowerShellTab.Files.SelectedFile.Editor.Text;
                    if ((s != null) && (s != string.Empty))
                    {
                        Token[] tk;
                        ParseError[] errors;
                        ScriptBlockAst sba;
                        sba = Parser.ParseInput(s, out tk, out errors);
                        if (errors.Length != 0)
                        {
                            foreach (ParseError pe in errors)
                            {
                                ErrorPosition ep = new ErrorPosition(pe.Message, pe.Extent.StartLineNumber, pe.Extent.StartColumnNumber);
                                MyErrorsList.Add(ep);
                            }
                            MyComboBox.SelectedItem = "Errors";
                        }
                        else
                        {
                            IEnumerable<Ast> functions = sba.FindAll(o => o is System.Management.Automation.Language.FunctionDefinitionAst, true);
                            foreach (FunctionDefinitionAst a in functions)
                            {
                                string functionName = a.Name;
                                FunctionPosition fp = new FunctionPosition(functionName, a.Extent.StartLineNumber, a.Extent.EndLineNumber);
                                bool bFound = false;
                                foreach (FunctionPosition fpSearch in MyFunctionsList)
                                {
                                    if(fpSearch.lineStart == fp.lineStart)
                                        bFound = true;
                                }
                                if (!(bFound))
                                {
                                    MyFunctionsList.Add(fp);
                                }
                            }
                            if ((variableName != null) && (variableName != string.Empty))
                            {
                                IEnumerable<Ast> variables = sba.FindAll(o => o is System.Management.Automation.Language.VariableExpressionAst, true);
                                foreach (VariableExpressionAst a in variables)
                                {
                                    if (a.VariablePath.UserPath == variableName)
                                    {
                                        VariablePosition vp = new VariablePosition(a.VariablePath.UserPath, a.Extent.StartLineNumber, a.Extent.StartColumnNumber, a.Extent.EndColumnNumber);
                                        MyVariableList.Add(vp);
                                    }
                                }
                            }

                            int lineNb = 1;
                            int regionNb = 0;
                            int todoNb = 0;
                            foreach (string line in s.Split('\n'))
                            {
                                Match mRegion = Regex.Match(line, @"^#region\s*(.*)$");
                                if(mRegion.Success)
                                {
                                    string regionName = string.Empty;
                                    if (mRegion.Groups[1].Value == string.Empty)
                                    {
                                        regionName = string.Format("Unnamed region #{0}", regionNb);
                                        regionNb++;
                                    }
                                    else
                                    {
                                        regionName = mRegion.Groups[1].Value;
                                        regionName = regionName.TrimEnd('\r', '\n');
                                    }
                                    MyRegionsList.Add(new RegionPosition(regionName, lineNb, 1));
                                }
                                Match mTODOs = Regex.Match(line, @"^.*#\s*[tT][oO][dD][oO]\s*(?::\s+){0,1}(.*)$");
                                if (mTODOs.Success)
                                {
                                    string todoName = string.Empty;
                                    if (mTODOs.Groups[1].Value == string.Empty)
                                    {
                                        todoName = string.Format("Unnamed TODO #{0}", regionNb);
                                        todoNb++;
                                    }
                                    else
                                    {
                                        todoName = mTODOs.Groups[1].Value;
                                        todoName = todoName.TrimEnd('\r', '\n');
                                    }
                                    MyTODOsList.Add(new TODOPosition(todoName, lineNb, 1));
                                }
                                lineNb++;
                            }
                        }
                    }
                }
            }
        }

        private void MyList_SelectionChanged_1(object sender, SelectionChangedEventArgs e)
        {
            if (e.AddedItems != null)
            {
                Object oSelected = null;
                if (e.AddedItems.Count > 0)
                {
                    oSelected = e.AddedItems[0];
                }
                if(oSelected != null)
                {
                    ((IJumpable)oSelected).JumpTo(HostObject);
                    // Unselect the item so we can select it again
                    MyListView.SelectedIndex = -1;
                }
            }
        }

        private void MyComboBox_Selected_1(object sender, RoutedEventArgs e)
        {
            ParseCodeAndFillList();
            // TODO if using a list of lists, this would be much cleaner...
            if((string)(((ComboBox)sender).SelectedItem) == "Functions")
            {
                MyListView.ItemsSource = MyFunctionsList;
            }
            else if ((string)(((ComboBox)sender).SelectedItem) == "TODO")
            {
                MyListView.ItemsSource = MyTODOsList;
            }
            else if ((string)(((ComboBox)sender).SelectedItem) == "Regions")
            {
                MyListView.ItemsSource = MyRegionsList;
            }
            else if ((string)(((ComboBox)sender).SelectedItem) == "Errors")
            {
                MyListView.ItemsSource = MyErrorsList;
            }
            else if ((string)(((ComboBox)sender).SelectedItem) == "Variable")
            {
                MyListView.ItemsSource = MyVariableList;
            }
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            ParseCodeAndFillList();
        }
    }
    public interface IJumpable
    {
        void JumpTo(ObjectModelRoot ISE);
    }
    public class FunctionPosition: IComparable, IJumpable
    {
        public string name { get; set; }
        public int lineStart { get; set; }
        public int lineEnd { get; set; }
        public string helpMsg { get; set; }
        public int line { get; set; }
        public FunctionPosition(string name, int start, int end)
        {
            this.name = name;
            this.lineStart = start;
            this.lineEnd = end;
            this.line = this.lineStart;
            this.helpMsg = string.Empty;
        }
        public void JumpTo(ObjectModelRoot ISE)
        {
            // TODO: try to have JumpTo show the target on the first line of the editor
            // First, check that we can actually jump there...
            if (lineStart < (ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.LineCount + 1))
            {
                ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.SetCaretPosition(ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.LineCount, 1);
                ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.SetCaretPosition(this.lineStart, 1);
                ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.Focus();
                ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.EnsureVisible(lineStart);
            }
        }
        public override string ToString()
        {
            return name;
        }

        int IComparable.CompareTo(object obj)
        {
            FunctionPosition f = (FunctionPosition)obj;
            return String.Compare(this.name, f.name);
        }
    }

    public class ErrorPosition : IJumpable
    {
        public string message { get; set; }
        public string name { get; set; }
        public int line { get; set; }
        public int column { get; set; }
        public ErrorPosition(string msg, int lin, int col)
        {
            this.message = msg;
            this.name = message;
            this.line = lin;
            this.column = col;
        }

        public void JumpTo(ObjectModelRoot ISE)
        {
            // TODO: try to have JumpTo show the target on the first line of the editor
            if (line < (ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.LineCount + 1))
            {
                ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.SetCaretPosition(ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.LineCount, 1);
                if (column < ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.GetLineLength(line))
                {
                    ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.SetCaretPosition(line, column);
                }
                else
                {
                    ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.SetCaretPosition(line, 1);
                }
                ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.EnsureVisible(line);
            }
        }
        public override string ToString()
        {
            return message;
        }
    }
    public class RegionPosition : IJumpable, IComparable
    {
        public string name { get; set; }
        public int line { get; set; }
        public int column { get; set; }
        public RegionPosition(string n, int lin, int col)
        {
            this.name = n;
            this.line = lin;
            this.column = col;
        }

        public void JumpTo(ObjectModelRoot ISE)
        {
            // TODO: try to have JumpTo show the target on the first line of the editor
            if (line < (ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.LineCount + 1))
            {
                ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.SetCaretPosition(ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.LineCount, 1);
                if (column < ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.GetLineLength(line))
                {
                    ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.SetCaretPosition(line, column);
                }
                else
                {
                    ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.SetCaretPosition(line, 1);
                }
                ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.EnsureVisible(line);
                ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.Focus();
            }
        }
        public override string ToString()
        {
            return name;
        }
        int IComparable.CompareTo(object obj)
        {
            RegionPosition f = (RegionPosition)obj;
            return String.Compare(this.name, f.name);
        }
    }
    public class TODOPosition : IJumpable, IComparable
    {
        public string name { get; set; }
        public int line { get; set; }
        public int column { get; set; }
        public TODOPosition(string n, int lin, int col)
        {
            this.name = n;
            this.line = lin;
            this.column = col;
        }

        public void JumpTo(ObjectModelRoot ISE)
        {
            // TODO: try to have JumpTo show the target on the first line of the editor
            if (line < (ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.LineCount +1))
            {
                ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.SetCaretPosition(ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.LineCount, 1);
                if (column < ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.GetLineLength(line))
                {
                    ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.SetCaretPosition(line, column);
                }
                else
                {
                    ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.SetCaretPosition(line, 1);
                }
                ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.EnsureVisible(line);
                ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.Focus();
            }
        }
        public override string ToString()
        {
            return name;
        }
        int IComparable.CompareTo(object obj)
        {
            TODOPosition f = (TODOPosition)obj;
            return String.Compare(this.name, f.name);
        }
    }
    public class VariablePosition : IJumpable, IComparable
    {
        public string name { get; set; }
        public int line { get; set; }
        public int column { get; set; }
        public int endColumn { get; set; }
        public VariablePosition(string n, int lin, int col, int endCol)
        {
            this.name = n;
            this.line = lin;
            this.column = col;
            this.endColumn = endCol;
        }

        public void JumpTo(ObjectModelRoot ISE)
        {
            // TODO: try to have JumpTo show the target on the first line of the editor
            if (line < (ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.LineCount + 1))
            {
                ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.SetCaretPosition(ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.LineCount, 1);
                if (column < ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.GetLineLength(line))
                {
                    ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.SetCaretPosition(line, column);
                }
                else
                {
                    ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.SetCaretPosition(line, 1);
                }
                ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.EnsureVisible(line);
                ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.Focus();
                // TODO that doesn't work... Why?
                ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.Select(line, column, line, endColumn);
                //ISE.CurrentPowerShellTab.Files.SelectedFile.Editor.Select(1, 2, 1, 3);
            }
        }
        public override string ToString()
        {
            return name;
        }
        int IComparable.CompareTo(object obj)
        {
            TODOPosition f = (TODOPosition)obj;
            return String.Compare(this.name, f.name);
        }
    }
}
